
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/pagemap.h>
#include <asm/uaccess.h>
#include <linux/mm.h>

#include "mapmod.h"
#include "moddefs.h"
#include "fileop.h"
#include "chdev.h"


extern struct chdev_data device_data[];
extern struct sw_encoder *  encoder;

int set_control( struct file* file, const struct UserPages* uPages )
{
	int pageCount = 0;
	int i = 0;
	void* upage;
	struct page* *  pages = NULL;
	struct chdev_data* data = (struct chdev_data*)file->private_data;

	pages = kcalloc( uPages->nr_pages, sizeof(*pages), GFP_KERNEL );
	pageCount = get_user_pages_fast( (unsigned long) uPages->ptr,
			uPages->nr_pages, 1, pages
			);
	if ( pageCount == uPages->nr_pages ) {
		data->pages = (void**)pages;
		data->nr_pages = pageCount;
	} else {
		kfree( pages );
		data->pages = NULL;
		data->nr_pages = -1;
	}
	if ( data->nr_pages>0 ) {
		printk( KERN_NOTICE "Kernel allocate %d user pages\n", data->nr_pages );
		for ( i = 0; i < data->nr_pages; ++i ) {
			//printk( KERN_NOTICE "\t%d: %lx\n", i, page_to_pfn( pages ) );
			upage = kmap( pages[i] );
			if ( upage ) {
				printk( KERN_NOTICE "\t%d: %lx -'%c'\n", i, page_to_pfn( pages[i] ), *((char*)upage) );
				kunmap( pages[i] );
			} else {
				printk( KERN_NOTICE "\t%d: %lx - no data access\n", i, page_to_pfn( pages[i] ) );
			}
		}
	} else {
		printk( KERN_NOTICE "Kernel can't allocate %lu user pages\n", uPages->nr_pages );
	}
	return 0;
}

void release_control( struct file* file ) {
	struct chdev_data* data = (struct chdev_data*)file->private_data;
	int counter = data->nr_pages, i = 0;
	struct page* * pages = (struct page**)data->pages;
	for (;i<counter;++i) {
		SetPageDirty( pages[i] );
		page_cache_release( pages[i] );
	}
	kfree( pages );
	data->pages = NULL;
	data->nr_pages = -1;
	return;
}

int fop_open (struct inode* inode, struct file* file )
{
	int retval = 0;
	unsigned int iprv = 0;
	int minor = iminor( inode );
	int ind = minor - DEVICE_BASE_MINOR;
	struct chdev_data* data = &device_data[ ind ];
	file->private_data = (void*) data;
	iprv = data->used;
	if ( iprv == 0 ) {
		data->used = 1;
		printk( KERN_DEBUG "First open file %d\n", minor );
	} else {
		data->used = iprv + 1;
		printk( KERN_DEBUG "Next %u open file %d\n", iprv, minor );
	}
	return retval;
}

int fop_release(struct inode* inode, struct file* file )
{
	int minor = iminor( inode );
	// int ind = minor - DEVICE_BASE_MINOR;
	struct chdev_data* data = (struct chdev_data*)file->private_data;
	unsigned int iprv = data->used;
	iprv = iprv - 1;
	if ( iprv == 0 ) {
		printk( KERN_DEBUG "Final close file %d\n", minor );
	} else {
		printk( KERN_DEBUG "Close %u file %d\n", iprv, minor );
	}
	data->used = iprv;
	release_control( file );
	return 0;
}

void simple_vma_open(struct vm_area_struct *vma)
{
	printk( KERN_NOTICE "Simple VMA open.\n" );
}

void simple_vma_close(struct vm_area_struct *vma)
{
	printk(KERN_NOTICE "Simple VMA close.\n");
}

int simple_vma_fault(struct vm_area_struct* vma, struct vm_fault* vmf )
{
	struct chdev_data* data = vma->vm_private_data;
	void* vpage = (void*) data->page;
	struct page* page = vmalloc_to_page( vpage );
	printk( KERN_NOTICE "Simple VMA fault.\n");
	printk( KERN_NOTICE "pgoff: %lu; max_pgoff: %lu; virtual_address: %p.\n", vmf->pgoff, vmf->max_pgoff, vmf->virtual_address );
	vmf->page = page;
	return 0;
}

static struct vm_operations_struct simple_remap_vm_ops = {
	.open =  simple_vma_open,
	.close = simple_vma_close,
//	.fault = simple_vma_fault
};

int fop_mmap(struct file* file, struct vm_area_struct* vma)
{
	struct chdev_data* data = file->private_data;
	void* vpage = (void*) data->page;
	struct page* page = vmalloc_to_page( vpage );

//	printk( KERN_NOTICE "Page at virt %p, pfn %lx(%lx:%p)\n", vpage, page_to_pfn(page), __pa(vpage), (void*)virt_to_phys(vpage) );
//	remap_pfn_range( vma, vma->vm_start, page_to_pfn(page), vma->vm_end - vma->vm_start, vma->vm_page_prot );
//	printk( KERN_NOTICE "vma->vm_pgoff: %lu\n", vma->vm_pgoff );

	if ( remap_pfn_range( vma, vma->vm_start, page_to_pfn(page), vma->vm_end - vma->vm_start, vma->vm_page_prot ) )
		return -EAGAIN;

	vma->vm_ops = &simple_remap_vm_ops;
	vma->vm_private_data = file->private_data;

	simple_vma_open(vma);
	return 0;
}

ssize_t fop_read ( struct file* file, char __user* chbuf, size_t size, loff_t* offset)
{
	struct chdev_data* data = (struct chdev_data*) file->private_data;
	const char* page = (const char*)data->page;
	ssize_t lost = PAGE_SIZE - *offset;
	size_t copy = min_t(size_t,size,(lost<1)?0:lost);
	lost = copy_to_user( chbuf, page + *offset, copy );
	printk ( KERN_DEBUG "Copy %lu bytes (off:%lld,req:%lu,pid:%d), lost %lu (%c)\n", copy, *offset, size, task_pid_nr(current), lost, *(page+512) );
	*offset += ( copy -  lost );
	return ( copy -lost );
}

ssize_t fop_write (struct file* file, const char __user* chbuf, size_t size, loff_t* offset )
{
	struct chdev_data* data = (struct chdev_data*) file->private_data;
	const char* page = (const char*)data->page;
	ssize_t lost = 0;
	ssize_t copy = PAGE_SIZE - *offset;
	copy = min_t(ssize_t,copy,size);
	printk ( KERN_DEBUG "Write %lu bytes (off:%lld,req:%lu)\n", copy, *offset, size );
	if ( copy < 0 ) {
		return -ENOMEM;
	}
	lost = copy_from_user( (void*)(page + *offset), chbuf, copy );
	*offset += ( copy - lost );
	printk( KERN_DEBUG "Data successfully writen: %ld\n", copy - lost );
	return ( copy - lost );
}

long fop_ioctl ( struct file * file, unsigned int cmd, unsigned long data )
{
	long	retval = 0;
	int		err = 0;
	size_t	size = 0;
	unsigned long lost;
	struct	UserPages uPage;

    if ( _IOC_TYPE(cmd) != MAPMOD_IOC_MAGIC ) {
    	return -ENOTTY;
    }

    if ( _IOC_NR(cmd) > MAPMOD_IOC_MAXNR ) {
    	return -ENOTTY;
    }

    if ( _IOC_DIR(cmd) & _IOC_READ ) {
            err		= !access_ok( VERIFY_WRITE, (void __user *)data, _IOC_SIZE(cmd) );
    } else if ( _IOC_DIR(cmd) & _IOC_WRITE ) {
            err		= !access_ok( VERIFY_READ, (void __user *)data, _IOC_SIZE(cmd) );
    }

    if (err) {
    	return -EFAULT;
    }

	switch (cmd) {
	case	MAPMOD_IOCTL_SET_BUFFER:
		size	= _IOC_SIZE( MAPMOD_IOCTL_SET_BUFFER );
		lost = copy_from_user( (void*) &uPage, (void*)data, size );
		if ( lost > 0 ) {
			printk( KERN_NOTICE "Can't copy data from userspace\n" );
			retval = -ENOTTY;
			break;
		}
		retval = set_control( file, &uPage );
		printk( KERN_NOTICE "MAPMOD_IOCTL_SET_BUFFER: user buffer start at address: %p\n", (void*) uPage.ptr );
		break;
	case MAPMOD_IOCTL_RESET_BUFFER:
		release_control( file );
		printk( KERN_NOTICE "MAPMOD_IOCTL_RESET_BUFFER: user buffer start at address: %p\n", (void*) data );
		break;
	case MAPMOD_IOCTL_ROTATE_START:
		hrtimer_start( &encoder->timer, encoder->interval, HRTIMER_MODE_REL );
		break;
	case MAPMOD_IOCTL_ROTATE_STOP:
		if ( hrtimer_active(&encoder->timer) ) {
			hrtimer_cancel(&encoder->timer);
		}
		break;
	case MAPMOD_IOCTL_SET_RPM12:
		encoder->interval = ktime_set( 0, 1220703 );
		break;
	case MAPMOD_IOCTL_SET_RPM6:
		encoder->interval = ktime_set( 0, 2441406 );
		break;
	case MAPMOD_IOCTL_SET_RPM4:
		encoder->interval = ktime_set( 0, 3662109 );
		break;
	case MAPMOD_IOCTL_SET_RPM2:
		encoder->interval = ktime_set( 0, 7324218 );
		break;
	default:
		printk( KERN_WARNING "IOCTL: undefined command value %d\n", cmd );
		retval	= -ENOTTY;
		break;
	};

    return retval;
}

loff_t fop_llseek (struct file* file, loff_t pos, int from )
{
	loff_t	newpos = 0;

	switch (from) {
	case 0: /* SEEK_SET */
		newpos	= (pos>=PAGE_SIZE)?-EINVAL:pos;
		break;
	case 1: /* SEEK_CUR */
		newpos	= file->f_pos + pos;
		if ( newpos>=PAGE_SIZE ) {
			newpos = -EINVAL;
		}
		break;
	case 2: /* SEEK_END */
		//newpos = BUF_SIZE + off;
		newpos = PAGE_SIZE - pos;
		break;
	default: /* не может произойти */
		return -EINVAL;
	}
	if (newpos < 0){
		return -EINVAL;
	}
	file->f_pos	= newpos;

	return newpos;
}
